// project main.go
package main

import (
	"bytes"
	"encoding/hex"
	"fmt"
	"log"

	"os"
	"time"
)

const (
	ShmName    string = "shm_svsub"
	SmpDataCnt int    = 0x10000
	SmpDataLen int    = 64
)

var Conf *Config
var Shmem []byte

func RoutineGooseSub(sock *Rawsock) {
	goose_addr_sub, _ := hex.DecodeString(Conf.GooseSub.Addr)
	sv_addr_sub, _ := hex.DecodeString(Conf.SvSub.Addr)

	if Conf.GooseSub.Enabled {
		sock.SetMulticastFilter(goose_addr_sub)
	} else {
		return
	}
	if Conf.SvSub.Enabled {
		sock.SetMulticastFilter(sv_addr_sub)
	}

	for {
		pkt := <-sock.ChanRx
		if bytes.Equal(pkt[:6], goose_addr_sub) {
			header, apdu := UnpackEtherPacket(pkt)
			if header.TypeId == GOOSE_TYPE_ID {
				godec, err := DecodeGoose(apdu)
				if err != nil {
					log.Fatal(err)
				} else {
					log.Printf("%+v\n", godec)
				}
			}
		}
		if bytes.Equal(pkt[:6], sv_addr_sub) {
			header, apdu := UnpackEtherPacket(pkt)
			if header.TypeId == SV_TYPE_ID {
				svdec, err := DecodeSv(apdu)
				if err != nil {
					log.Fatal(err)
				} else {
					log.Printf("%+v\n", svdec)
				}
			}
		}
	}
}

func RoutineGoosePub(sock *Rawsock) {
	if Conf.GoosePub.Enabled == false {
		return
	}
	goose_addr_pub, _ := hex.DecodeString(Conf.GoosePub.Addr)
	goenc := GooseDOApp{
		GocbRef:            "E1Q1SB1C1/LLN0$GO$GbAscControl",
		TimeToLive:         2,
		DataSet:            "E1Q1SB1C1/LLN0$DsAscControl",
		GoID:               "AscCtrl",
		UtcTime:            [8]byte{0},
		StNum:              1,
		SqNum:              0,
		Test:               false,
		ConfRev:            0,
		NeedsCommissioning: false,
		NumDataSetEntries:  8,
		AllData:            AllDataDO{false, false, false, false, false, false, false, false},
	}
	header := EtherHeader{
		DstHwaddr: goose_addr_pub,
		LocHwaddr: sock.Hwaddr[0:],
		TypeId:    GOOSE_TYPE_ID,
		AppId:     Conf.GoosePub.AppId,
	}
	gsCtrl := NewGooseCtrl(&goenc.AllData)
	// 先发一帧初始状态
	EncodeUtcTime(time.Now(), goenc.UtcTime[0:])
	apdu, err := EncodeGoose(&goenc)
	if err != nil {
		log.Fatal(err)
	} else {
		pkt := PackEtherPacket(header, apdu)
		sock.ChanTx <- pkt
		sock.EmitWriteEvent()
	}

	for {
		changed, timeout := gsCtrl.PollStatChange()
		if changed || timeout {
			if changed {
				goenc.StNum += 1
				goenc.SqNum = 0
			}
			if timeout {
				goenc.SqNum += 1
			}
			goenc.TimeToLive = 2 * int(gsCtrl.retrans[gsCtrl.retrIdx])
			EncodeUtcTime(gsCtrl.lastChangeTm, goenc.UtcTime[0:])
			goenc.AllData = gsCtrl.allData
			apdu, err := EncodeGoose(&goenc)
			if err != nil {
				log.Fatal(err)
			} else {
				pkt := PackEtherPacket(header, apdu)
				sock.ChanTx <- pkt
				sock.EmitWriteEvent()
			}
		}

		if gsCtrl.retrans[gsCtrl.retrIdx] < 100 {
			time.Sleep(gsCtrl.retrans[gsCtrl.retrIdx] * time.Millisecond)
		} else {
			time.Sleep(100 * time.Millisecond)
		}
	}
}

func RoutineSvSub(sock *Rawsock) {
	sv_addr_sub, _ := hex.DecodeString(Conf.SvSub.Addr)

	if Conf.SvSub.Enabled {
		sock.SetMulticastFilter(sv_addr_sub)
	}

	buf := make([]byte, 1024)
	var pkt []byte
	var curCnt int = 0
	go RoutineSvTest()	// 测试用，可删除。RoutineSvSub订阅接收报文数据，简单解析后存入ShmName的共享内存。本函数通过该段共享内存获取SV接收的数据，并解析采样值并计算幅值相位

	for {
		recv_len, _ := sock.Recv(buf[0:])
		if recv_len > 22 {
			pkt = buf[:recv_len]
			if bytes.Equal(pkt[:6], sv_addr_sub) {
				header, apdu := UnpackEtherPacket(pkt)
				if header.TypeId == SV_TYPE_ID {
					svdec, err := DecodeSvFast(apdu)
					if err != nil {
						log.Fatal(err)
					} else {
						curCnt = int(DecodeUint(svdec.SequenceOfASDU[0].SmpCnt[0:]))
						offset := curCnt * SmpDataLen
						copy(Shmem[offset:offset+SmpDataLen], svdec.SequenceOfASDU[0].SmpData[:SmpDataLen])
					}
				}
			}
		}
	}
}


func main() {
	Conf = LoadConfig()
	if Conf == nil {
		fmt.Printf("Load config file error: %s\n", ConfigFile)
		os.Exit(-1)
	}
	var err error
	Shmem, err = ShmOpen(ShmName, SmpDataCnt*SmpDataLen)
	if Shmem == nil {
		fmt.Println(err.Error())
		os.Exit(-1)
	}

	Sock, err := NewRawsock(Conf.IFace)
	if Sock == nil {
		fmt.Println(err.Error())
		os.Exit(-1)
	}

	if Conf.GooseSub.Enabled {
		go RoutineGooseSub(Sock) // GOOSE订阅
	}
	if Conf.GoosePub.Enabled {
		go RoutineGoosePub(Sock) // GOOSE发布
	}
	if Conf.GooseSub.Enabled || Conf.GoosePub.Enabled { // 由于SV的数据包太多，EventLoop收发效率不够；故SV采用简单高效的Recv循环，不可将GOOSE和SV同时在一个网口激活
		Sock.EventLoop()
	} else if Conf.SvSub.Enabled {	// 配置文件中将GOOSE订阅和发布都禁用后，才能使能SV订阅功能
		RoutineSvSub(Sock) // SV订阅
	}
}
